package com.taotao.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;

import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.taotao.config.SentineLManage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * @author wangjin
 * @title: LimitController
 * @projectName sw-gateway
 * @description:
 * @date 2021/12/9 18:57
 */
@RestController
@Slf4j
public class LimitController {
    //qps限流
    @Autowired
    private SentineLManage sentineLManage;

    /**
     * 初始化限流策略
     * 1.限制服务器 qps  当调用该api的QPS达到阈值的时候，进行限流；
     * 当QPS超过阈值，会采取措施进行流控，策略包含：直接拒绝、Warm Up、匀速排队。
     * （1）直接拒绝：为默认的流控方式：直接拒绝（RuleConstant.CONTROL_BEHAVIOR_DEFAULT），拒绝后抛出FlowException。适用于系统处理能力已知（系统处理能力的水位已知）
     * （2）Warm Up方式，即预热/冷启动方式。通过冷启动，让通过的流量缓慢增加，逐渐增加到阈值上线（给系统一个预热时间），避免冷启动系统被压垮。
     * （3）匀速排队：让请求匀速通过，对应的是令牌桶算法（注意：匀速排队模式暂时不支持 QPS > 1000 的场景。）
     *
     * @return
     */
    @RequestMapping("/initFlowQpsRule2")
    public String initFlowQpsRule2() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        //创建限流资源
        rule1.setResource("getSentinelThreads");
        //线程数是2
        rule1.setCount(2);
        //RuleConstant.FLOW_GRADE_QPS
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //限流应用default
        rule1.setLimitApp("default");
        //添加该限流规则
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        return "限流配置初始化成功";

    }

    /**
     * 演示getSentinelQps QPS 对接口限流
     *
     * @return
     */
    @RequestMapping("/getSentinelQps")
    public String getSentinelQps() {
        Entry entry = null;
        try {
            entry = SphU.entry("getSentinelQps");
            // 执行我们服务需要保护的业务逻辑
            log.info("正常执行业务逻辑代码...");
            return "getSentinelQps";
        } catch (Exception e) {
            log.error("<e:{}>", e);
            return "该接口访问频率过多，请稍后重试!";
        } finally {
            // SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
            if (entry != null) {
                entry.exit();
            }
        }
    }

    /**
     * 并发限流
     * 限制服务器端同时处理线程数量 基于并发数限流 当调用该api的线程数达到阈值的时候，进行限流。
     * 1.并发数控制用于保护业务线程池不被慢调用耗尽，可以采用线程池隔离的隔离方案（不同业务使用不同的线程池）；
     * 2.sentinel采用的方法是，统计当前请求上下文的线程数据（正在执行的调用数据），超出阈值就会拒绝新的请求，效果类似于“信号量”隔离（信号量适用于那些资源有明确访问数量限制的场景，常用于限流 ）。
     */
    //初始化限流路由策略
    @RequestMapping("/initFlowQpsRule3")
    public String initFlowQpsRule3() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        // 创建限流资源
        rule1.setResource("getSentinelThreads");
        // 线程数是为2
        rule1.setCount(2);
        // 线程数类型
        rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        //限流应用default
        rule1.setLimitApp("default");
        //添加该限流规则
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        return "....限流配置初始化成功..";
    }

    /**
     * 演示getSentinelThreads  线程数限流
     *
     * @return
     */
    @RequestMapping("/getSentinelThreads")
    public String getSentinelThreads() {
        Entry entry = null;
        try {
            entry = SphU.entry("getSentinelThreads");
            // 执行我们服务需要保护的业务逻辑
            log.info("正常执行业务逻辑代码...");
            Thread.sleep(3000);
            return "getSentinelQps";
        } catch (Exception e) {
            log.error("<e:{}>", e);
            return "服务器端忙，请稍后重试!";
        } finally {
            // SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
            if (entry != null) {
                entry.exit();
            }
        }
    }

    /**
     * 注解形式配置api限流
     */
    @RequestMapping("sentinelAnnotationQps")
    @SentinelResource(value = "getSentinelQps", blockHandler = "getSentinelQpsException")
    public String sentinelAnnotationQps() {
        return "sentinelAnnotation";
    }


    /**
     * 被限流后返回的提示
     *
     * @param e
     * @return
     */
    public String getSentinelDashboardQpsException(BlockException e) {
        log.error("<e:{}>", e);
        return "该接口已经被限流啦!";
    }

    /**
     * 整合getSentinelDashboardQps 仪表盘
     *
     * @return
     */
    @SentinelResource(value = "getSentinelDashboardQps", blockHandler = "getSentinelDashboardQpsException")
    @RequestMapping("/getSentinelDashboardQps")
    public String getSentinelDashboardQps() throws InterruptedException {
        Thread.sleep(3000);
        return "getOrderDashboard";
    }

    /**
     * 整合getSentinelDashboardThreads 仪表盘
     *
     * @return
     */
    @SentinelResource(value = "getSentinelDashboardThreads", blockHandler = "getSentinelDashboardQpsException")
    @RequestMapping("/getSentinelDashboardThreads")
    public String getSentinelDashboardThreads() throws InterruptedException {
        log.info("<正在开始执行业务逻辑代码...>");
        // Thread.sleep(3000);
        return "getSentinelDashboardThreads";
    }
    //****************************************************************************

    //关联限流

    //****************************************************************************

    /**
     * 整合getSentinelDashboardQps 仪表盘
     *
     * @return
     */
    @SentinelResource(value = "getSentinelDashboardQpsA", blockHandler = "getSentinelDashboardQpsException")
    @RequestMapping("/getSentinelDashboardQpsA")
    public String getSentinelDashboardQpsA() throws InterruptedException {
        log.info("<getSentinelDashboardQpsA>");
        Thread.sleep(3000);
        return "getSentinelDashboardQpsA";
    }

    /**
     * 整合getSentinelDashboardQps 仪表盘
     *
     * @return
     */
    @SentinelResource(value = "getSentinelDashboardQpsB", blockHandler = "getSentinelDashboardQpsException")
    @RequestMapping("/getSentinelDashboardQpsB")
    public String getSentinelDashboardQpsB() throws InterruptedException {
        log.info("<getSentinelDashboardQpsB>");
        Thread.sleep(3000);
        return "getSentinelDashboardQpsB";
    }

    //****************************************************************************

    //链路限流

    //****************************************************************************
    @RequestMapping("/getSentinelA")
    public String getSentinelA() {
        sentineLManage.show();
        return "getSentinelA";
    }


    @RequestMapping("/getSentinelB")
    public String getSentinelB() {
        log.info("getSentinelB");
        sentineLManage.show();
        return "getSentinelB";
    }

    @RequestMapping("/login")
    public String login() {
        log.info("login");
        //sentinelManage.show();
        return "login";
    }
    //****************************************************************************

    //服务雪崩

    //****************************************************************************

    /**
     * 演示服务雪崩效应
     *
     * @return
     */
    @RequestMapping("getMayiktA")
    @SentinelResource(value = "getMayiktA", blockHandler = "getSentinelDashboardQpsException")
    public String getMayiktA() throws InterruptedException {
        // 模拟该接口处理需要5s
        log.info("...正在处理业务逻辑getMayiktA....");
        //Thread.sleep(50000);
        return "mayikt";
    }

    /**
     * 另外接口
     *
     * @return
     */
    @SentinelResource(value = "getMayiktB", blockHandler = "getSentinelDashboardQpsException")
    @RequestMapping("/getMayiktB")
    public String getMayiktB() {
        log.info("...正在处理业务逻辑getMayiktB....");
        return "mayiktB";
    }

    //****************************************************************************

    //服务降级 RT(平均响应时间策略)

    //****************************************************************************

    /**
     * rtDemo01
     *
     * @return
     */
    @GetMapping("/rtDemo01")
    @SentinelResource(value = "rtDemo01", fallback = "rtDemo01Fallback")
    public String rtDemo01() throws InterruptedException {
        log.info("<rtDemo01>");
        Thread.sleep(200);
        return "rtDemo01";
    }

    /**
     * 服务降级回调方法
     *
     * @return
     */
    public String rtDemo01Fallback() {
        log.info("服务降级啦...");
        return "当前接口处理忙，请稍后重试!";
    }
    //****************************************************************************

    //服务降级 异常比例
    //
    //当资源的每秒请求量>= 5，并且每秒异常总数占通过量的比值超过阈值（DegradeRule 中的 count）之后，资源进入降级状态，即在接下的时间窗口（DegradeRule 中的 timeWindow，以 s 为单位）之内，对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0]，代表 0% - 100%

    //****************************************************************************

    /**
     * 异常比例
     *
     * @return
     */
    @RequestMapping("/getMayiktError")
    @SentinelResource(value = "getMayiktError", fallback = "getMayiktErrorFallback")
    public String getMayiktError(Integer age) {
        log.info("<getMayiktError>");
        Integer j = 1 / age;
        return "getMayiktError:" + j;
    }


    public String getMayiktErrorFallback(Integer age) {
        return "当前该接口错误次数太多，暂时无法访问!";
    }
    //服务降级 异常数
    //
    //当资源近1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的，若 timeWindow 小于 60s，则结束熔断状态后仍可能再进入熔断状态
    //****************************************************************************


    /**
     * 异常数
     *
     * @return
     */
    @RequestMapping("/getMayiktErrorCount")
    @SentinelResource(value = "getMayiktErrorCount", fallback = "getMayiktErrorCountFallback")
    public String getMayiktErrorCount(Integer age) {
        log.info("<getMayiktErrorCount>");
        Integer j = 1 / age;
        return "getMayiktErrorCount:" + j;
    }


    public String getMayiktErrorCountFallback(Integer age) {
        return "当前该接口错误次数太多，暂时无法访问!";
    }

    //何为热点？热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据，并对其访问进行限制。比如：
    //商品 ID 为参数，统计一段时间内最常购买的商品 ID 并进行限制
    //用户 ID 为参数，针对一段时间内频繁访问的用户 ID 进行限制
    //热点参数限流会统计传入参数中的热点参数，并根据配置的限流阈值与模式，对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制，仅对包含热点参数的资源调用生效。
    //相关代码:
    //****************************************************************************

    /**
     * 模拟秒杀接口
     *
     * @return
     */
    @RequestMapping("/seckill")
    @SentinelResource(value = "seckill", blockHandler = "seckillFallbackBlockHandler")
    public String seckill(Long userId) {
        log.info("<userId:{}>", userId);
        return "seckill:" + userId;
    }

    public String seckillFallbackBlockHandler(Long userId, BlockException exception) {
        log.info("<userId,{}您访问的频率太高，请稍后重试!>", userId);
        return "userId:" + userId + "您访问的频率太高，请稍后重试!";
    }
}
